package com.doupi.partner.util;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * SFTP 工具类
 * 
 * @author Super Jens Wang
 *
 * @version 1.0 @ 2016年7月20日 上午4:57:27
 */
public class SftpUtils {

	private static final Logger LOGGER = LoggerFactory.getLogger(SftpUtils.class);

	private final static String USERAUTH_GSSAPI_WITH_MIC = "userauth.gssapi-with-mic";
	private final static String NO = "no";
	private final static String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
	private final static int CONNECT_TIMEOUT = 20000;
	private final static String CHANNEL_TYPE = "sftp";
	private final static String ROOT_PATH = "/";
	private final static String DISCONNECT_ERROR_MSG = "关闭链接失败";
	private final static String DELET_FILE_ERROR_MSG = "删除文件[{}]失败";

	private ChannelSftp sftp;

	/** 主机 */
	private String host;

	/** 端口 */
	private int port;

	/** 用户名 */
	private String username;

	/** 密码 */
	private String password;

	public SftpUtils(String host, int port, String username, String password) {
		this.host = host;
		this.port = port;
		this.username = username;
		this.password = password;
	}

	/**
	 * 连接sftp服务器
	 */
	public SftpUtils connect() throws Exception {
		if (null == sftp) {
			JSch jsch = new JSch();
			Session sshSession = jsch.getSession(this.username, this.host, this.port);
			sshSession.setPassword(password);
			Properties sshConfig = new Properties();
			sshConfig.put(USERAUTH_GSSAPI_WITH_MIC, NO);
			sshConfig.put(STRICT_HOST_KEY_CHECKING, NO);
			sshSession.setConfig(sshConfig);
			sshSession.connect(CONNECT_TIMEOUT);
			Channel channel = sshSession.openChannel(CHANNEL_TYPE);
			sftp = (ChannelSftp) channel;
		}
		if (!sftp.isConnected()) {
			sftp.connect(CONNECT_TIMEOUT);
		}
		return this;
	}

	/**
	 * 断开
	 */
	public void disconnect() {
		if (null != sftp && sftp.isConnected()) {
			sftp.disconnect();
		}
	}

	// TODO 断开，并删除本地文件
	// public void disconnect(List<String> files) {
	// if (null != sftp && sftp.isConnected()) {
	// sftp.disconnect();
	// }
	// }

	/**
	 * 断开，并删除本地文件
	 */
	public void disconnect(List<File> files) {
		try {
			if (null != sftp && sftp.isConnected()) {
				sftp.disconnect();
			}
		} catch (Throwable e) {
			LOGGER.error(DISCONNECT_ERROR_MSG, e);
		}
		// 删除文件
		for (File file : files) {
			try {
				if (!file.exists() || !file.delete()) {
					LOGGER.info(DELET_FILE_ERROR_MSG, ToStringBuilder.reflectionToString(file));
				}
			} catch (Throwable e) {
				LOGGER.error(DELET_FILE_ERROR_MSG, ToStringBuilder.reflectionToString(file), e);
			}
		}
	}

	/**
	 * 上传单个文件
	 * 
	 * @param directory
	 *            上传的目录
	 * @param uploadFile
	 *            要上传的文件
	 */
	public SftpUtils upload(String directory, String uploadFile) throws Exception {
		File file = new File(uploadFile);
		upload(directory, new FileInputStream(file), file.getName());
		return this;
	}

	/**
	 * 上传单个文件
	 *
	 * @param directory
	 *            上传的目录
	 * @param inputStream
	 *            要上传的文件输入流
	 * 
	 * @param fileName
	 *            文件名
	 */
	public SftpUtils upload(String directory, InputStream inputStream, String fileName) throws Exception {
		try {
			cd(directory);
		} catch (Exception e) {
			LOGGER.error("进入目录 [{}] 失败", directory, e);
			String paths[] = StringUtils.split(directory, File.separatorChar);
			sftp.cd(ROOT_PATH);
			for (String path : paths) {
				try {
					cd(path);
				} catch (Throwable ef) {
					LOGGER.error("目录 [{}] 不存在，开始创建", path, ef);
					sftp.mkdir(path);
					cd(path);
				}
			}
		}
		sftp.put(inputStream, fileName);
		return this;
	}

	/**
	 * 下载单个文件
	 * 
	 * @param directory
	 *            下载目录
	 * @param downloadFile
	 *            下载的文件
	 * @param saveDirectory
	 *            存在本地的路径
	 */
	public SftpUtils download(String directory, String downloadFile, String saveDirectory) throws Exception {
		String saveFile = saveDirectory + File.separator + downloadFile;
		cd(directory);
		File file = new File(saveFile);
		sftp.get(downloadFile, new FileOutputStream(file));
		return this;
	}

	/**
	 * 下载目录下全部文件
	 * 
	 * @param directory
	 *            下载目录
	 * @param saveDirectory
	 *            存在本地的路径
	 */
	public SftpUtils downloadByDirectory(String directory, String saveDirectory) throws Exception {
		List<String> downloadFileList = listFiles(directory);
		for (String downloadFile : downloadFileList) {
			if (downloadFile.contains(".") && !downloadFile.startsWith(".")) {
				download(directory, downloadFile, saveDirectory);
			}
		}
		return this;
	}

	/**
	 * 删除文件
	 * 
	 * @param directory
	 *            - 要删除文件所在目录
	 * @param deleteFile
	 *            - 要删除的文件
	 */
	public SftpUtils delete(String directory, String deleteFile) throws Exception {
		return cd(directory).rm(deleteFile);
	}

	/**
	 * 删除文件
	 */
	public SftpUtils rm(String path) throws Exception {
		sftp.rm(path);
		return this;
	}

	/**
	 * 删除文件
	 */
	public SftpUtils rmdir(String path) throws Exception {
		sftp.rmdir(path);
		return this;
	}

	/**
	 * 列出目录下的文件
	 * 
	 * @param directory
	 *            - 要列出的目录
	 * @return list - 文件名列表
	 */
	public List<String> listFiles(String directory) throws Exception {
		List<String> fileNameList = new ArrayList<>();
		Vector<?> fileList = sftp.ls(directory);
		for (Object file : fileList) {
			String fileName = ((LsEntry) file).getFilename();
			if (StringUtils.equals(".", fileName) || StringUtils.equals("..", fileName)) {
				continue;
			}
			fileNameList.add(fileName);
		}

		return fileNameList;
	}

	/**
	 * 更改文件名
	 * 
	 * @param directory
	 *            - 文件所在目录
	 * @param oldFileNm
	 *            - 原文件名
	 * @param newFileNm
	 *            - 新文件名
	 */
	public SftpUtils rename(String directory, String oldFileNm, String newFileNm) throws Exception {
		sftp.cd(directory);
		sftp.rename(oldFileNm, newFileNm);
		return this;
	}

	/**
	 * 进入目录
	 */
	public SftpUtils cd(String directory) throws Exception {
		sftp.cd(directory);
		return this;
	}

	/**
	 * 获取输入流
	 */
	public InputStream getInputStream(String src) throws Exception {
		return sftp.get(src);
	}
}
